6W - EKS 파드에서 AWS 리소스 접근 제어
개요
eks에서 보안 관련하여 추가적으로 제공하는 기능 중 하나는 워크로드에서 AWS 리소스로 접근할 때의 보안이다.
가령 ALB Controller는 도대체 어떻게 ALB 리소스를 만들어주는 것인가?
이 문서에서는 클러스터에서 AWS 리소스를 접근하는 방법에 대해서 알아본다.
사전 지식
바로 위에서 말했듯 ALB Controller 같은 것들은 클러스터에서 구동되지만, 실제로 접근하는 리소스는 ALB로, AWS 상의 리소스이다.
이 컨트롤러가 리소스에 접근할 수 있도록 적절한 권한을 주는 작업이 필요한데 크게 3가지 방법이 있다.
가장 간단한 방법은, 사실 해당 컨트롤러가 실행되는 인스턴스에 권한을 부여하는 것이다.
ec2 인스턴스가 로드밸런서에 대한 권한을 가진다면 그 인스턴스에 위치한 어플리케이션이 요청을 날릴 때는 해당 인스턴스의 자격을 얻어서 날리기에 이러한 방법이 유효하다.
다만 이렇게 하면 해당 인스턴스의 모든 어플리케이션이 관련한 권한을 가지게 되니, 최소 권한 원칙을 조금도 준수할 수 없다.
IRSA(IAM Roles for Service Accounts)
그래서 나온 것이 IRSA, 즉 서비스 어카운트를 IAM Role과 매핑시키는 방식이다.
클러스터의 모든 워크로드라면 기본적으로 가지게 되는 서비스 어카운트를 IAM 롤에도 매핑을 시킨다.
구체적으로는 서비스 어카운트에 어노테이션으로 매핑되고자 하는 iam 롤을 작성한다.[1]
eks.amazonaws.com/role-arn: arn:aws:iam::xxxx:role/eksctl-eks-oidc-demo-addon-iamserviceaccount-Role1-H47XCR6FPRGQ
이런 식으로 롤의 arn이 어노테이션에 들어가도록 작성해주면 된다.
apiVersion: apps/v1
kind: Pod
metadata:
name: myapp
spec:
serviceAccountName: my-serviceaccount
containers:
- name: myapp
image: myapp:1.2
env:
- name: AWS_ROLE_ARN
value: arn:aws:iam::111122223333:policy/my-role
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
volumeMounts:
- mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
name: aws-iam-token
readOnly: true
volumes:
- name: aws-iam-token
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: sts.amazonaws.com
expirationSeconds: 86400
path: token
그러면 실제 이 서비스어카운트를 사용하는 워크로드는 실행시점에 Admission Control#Mutating & Validating Admission Webhook이 걸리는데, 이때 다음의 두 가지 세팅이 이뤄진다.[2]
- 환경변수로 해당 role의 arn, web identity token 파일 경로가 주입된다.
- projected 볼륨으로 서비스어카운트 토큰을 받게 되는데, 이 토큰은 OIDC 프로토콜의 ID 토큰 형식으로 돼있다.
- 토큰 생성 자체는 api 서버 차원에서 알아서 해주는 작업이다.
환경변수에 저 값이 들어가면 내부 컨테이너의 aws cli나 관련 라이브러리는 바로 STS로 요청이 날아간다.
이때 STS에서는 클러스터의 OIDC discovery 정보를 통해 신원이 제대로 검증된 것인지 체크한다.
신원 체크가 완료되면 해당 롤을 위한 임시 자격증명을 발급해주고, 이를 통해 aws의 리소스에 조작을 가할 수 있게 되는 것이다.
꽤나 복잡해보이지만, 결국 운영자 입장에서 설정해야 하는 사항은 다음으로 정리된다.
일단 첫번째로, 클러스터 OIDC 설정이 돼있어야 한다.
- iam 롤 만들어두기
- 원하는 권한을 적고, oidc 인증을 통해 assume role을 할 수 있도록 정책 설정
- 워크로드가 사용할 서비스 어카운트에 iam 롤에 대한 어노테이션 달기
나머지는 클러스터와 aws 차원에서 동작이 이뤄지므로, 이것만 세팅해주면 된다!
단점
AWS 측에서 검사하는 것은 그저 해당 토큰이 정말 IDP 제공자로부터 서명됐는지일 뿐이다.
이 토큰을 가지고 요청을 보낸 자가 누군지, 그래서 어떤 권한을 가져가려고 하는지는 확인하지 않는다.
그래서 토큰이 탈취됐을 때, 누구든 해당 롤이 가지는 권한을 획득할 수 있다.
그리고 아무래도 불편하다보니 실무 환경에서 종종 *를 이용해 모든 서비스 어카운트에 요청을 풀어둔다던가 하는 일이 있다.
이러면 더더욱 보안은 취약해진다.
그리고 애초에 어노테이션을 활용하는 방식 자체가 실수가 발생하기 쉽기도 하고 또 번거로운 과정이다.
여기에 OIDC 세팅까지 해야 하니(딸깍이긴 하지만).. 불필요하게 인증서를 외부에 노출하는 꼴이기도 하다.
EKS Pod Identity
그래서 나온 것이 또 파드 아이덴티티.
간단하게 애드온을 설치하면, 이번에도 뮤테이팅 어드미션 웹훅이 적용된다.
그래서 세팅해둔 서비스 어카운트를 사용하는 워크로드가 있을 때, 마찬가지로 환경변수와 볼륨이 세팅된다.
그러나 다음 동작부터가 조금 다른데, 내부 프로세스가 aws 요청을 날릴 때 자격증명을 애드온으로 같이 세팅된 pod identity agent 데몬셋에 요청한다.
그럼 이 에이전트는 파드 아이덴티티 전용 assume role을 eks api로 날리고, aws 상에 미리 세팅된 것이 있다면 이를 토대로 임시 자격 증명을 발급해주는 것이다.
이를 통해 워크로드는 aws 리소스를 조작할 권한을 가지게 된다.
데몬셋으로 된 에이전트는 hostNetwork를 사용한다.
그렇기 때문에 작동되는 모든 노드가 기본적으로 위의 정책을 가지고 있어야 한다.
그럼 미리 세팅한다는 것은 어떻게 하냐, 이렇게 pod identity association을 만들어주면 된다.
iam 롤에는 필요한 권한 세팅만 해주면 되고, 여기에서 어떤 서비스 어카운트가 관련한 세팅을 받아야 하는지 연결해주면 된다.
eksctl create podidentityassociation \
--cluster $CLUSTER_NAME \
--namespace default \
--service-account-name s3-sa \
--role-name s3-eks-pod-identity-role \
--permission-policy-arns arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--region ap-northeast-2
cli로는 이런 식으로 할 수 있다.
이 기능이 좋은 것이 태그를 기반으로 접근 가능한 리소스를 제한(ABAC)할 수 있다는 것이다.
노란 클러스터의 파드에는 노랑이라는 태그가 붙게 하면 해당 태그가 있는 리소스에만 접근이 가능한 방식이다.
위 사진으로 치면 위 컨테이너는 위의 AWS SM에만 접근이 가능할 것이다.
실습 진행
k get mutatingwebhookconfigurations.admissionregistration.k8s.io pod-identity-webhook -oyaml
뮤테이팅 어드미션 웹훅 설정에는 스킵을 하도록 설정된 파드 이외에 모든 파드의 생성 요청에 대해 적용되는 규칙이 설정돼있다.
스킵을 하도록 라벨을 달아두지 않으면, 모든 파드가 생성될 때 이 웹훅을 지나게 되는 것이다.
이에 따라 위에서 말했듯 파드에 환경변수와 볼륨이 적절히 들어가게 될 것이다.
url을 보았을 때, api 서버와 같은 인스턴스에 23443 포트에서 동작을 수행하는 것으로 보인다.
아무래도 클러스터 리소스로 만들었을 때는 AWS 사용자도 해당 컨테이너 정보를 확인할 수 있게 되기 때문에 따로 구동한 듯하다.
IRSA
테라폼 예시
module "vpc_cni_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "5.52.2"
role_name = "vpc-cni"
attach_vpc_cni_policy = true
vpc_cni_enable_ipv4 = true
vpc_cni_enable_cloudwatch_logs = true
force_detach_policies = true
oidc_providers = {
eks = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:aws-node"]
}
}
}
테라폼에서는 간단하게 세팅할 수 있는 모듈을 지원해주고 있다.
이를 이용해 간편하게 IRSA 설정을 할 수 있다.
그러나 이렇게만 보면 진짜 어떤 식으로 동작하는지 감을 잡기 어려울 수 있으니...
locals {
lbc_name_in_aws = "AWSLoadBalancerController"
lbc_name_in_cluster = "aws-load-balancer-controller"
lbc_namespace = "kube-system"
}
data "http" "lbc" {
url = "https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.11.0/docs/install/iam_policy.json"
}
resource "aws_iam_policy" "lbc" {
name = "${local.lbc_name_in_aws}IAMPolicy"
description = "IAM Policy for AWS Load Balancer Controller"
policy = data.http.lbc.response_body
}
data "aws_iam_policy_document" "lbc" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [data.aws_iam_openid_connect_provider.this.arn]
}
condition {
test = "StringEquals"
variable = "${replace(data.aws_iam_openid_connect_provider.this.url, "https://", "")}:sub"
values = [
"system:serviceaccount:${local.lbc_namespace}:${local.lbc_name_in_cluster}",
]
}
effect = "Allow"
}
}
resource "aws_iam_role" "lbc" {
name = "${local.lbc_name_in_aws}IAM"
assume_role_policy = data.aws_iam_policy_document.lbc.json
}
resource "aws_iam_role_policy_attachment" "lbc" {
role = aws_iam_role.lbc.name
policy_arn = aws_iam_policy.lbc.arn
}
직접 모든 걸 세팅한다면, 이런 식으로 만들어주면 된다.
해당 롤의 폴리시에서는 OIDC 검증을 할 수 있도록 AssumeRoleWithWebIdentity
액션을 취한다.
이렇게 하면 알아서 jwt 토큰의 값을 뜯어서 조건을 검사하게 된다.
resource "helm_release" "lbc" {
name = local.lbc_name_in_cluster
repository = "https://aws.github.io/eks-charts"
chart = "aws-load-balancer-controller"
namespace = local.lbc_namespace
set {
name = "clusterName"
value = local.cluster_name
}
set {
name = "region"
value = data.aws_region.current.name
}
set {
name = "vpcId"
value = data.aws_eks_cluster.this.vpc_config[0].vpc_id
}
set {
name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
value = module.alb_con_irsa.iam_role_arn
}
}
그 다음 위 롤에 매칭시키고 싶은 서비스어카운트에 어노테이션을 넣어준다.
직접 설정
apiVersion: v1
kind: Pod
metadata:
name: eks-iam-test1
spec:
containers:
- name: my-aws-cli
image: amazon/aws-cli:latest
command: ['tail', '-f', '/dev/null']
# automountServiceAccountToken: false
terminationGracePeriodSeconds: 0
간단하게 테스트를 진행할 수 있는 파드를 만들었다.
기본적으로 파드에 아무런 설정을 하지 않아 default 서비스 어카운트가 사용됐다.
아무런 설정을 하지 않으면 보다시피 aws cli를 사용할 수 없다.
물론 이건 credential 파일이 없어서 일어나는 이슈이긴 하다.
그럼 이제 IRSA 설정을 해보자.
eksctl create iamserviceaccount \
--name my-sa \
--namespace default \
--cluster $CLUSTER_NAME \
--approve \
--attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn' --output text)
위와 같이 굉장히 쉽게 설정이 가능하다.
직접 iam 롤도 만들고, 서비스 어카운트에 어노테이션도 붙이고 하는 작업을 전부 수작업으로 진행할 수도 있긴 하다.
위 테라폼 코드가 정확하게 그런 식으로 세팅한 예시이다.
하지만 아무래도 간단한 명령어를 이용하는 게 편할 것이다.
그럼 이렇게 알아서 서비스 어카운트가 만들어지고, 관련 어노테이션이 들어간다.
또한 iam에도 적절한 롤이 생성된 것을 확인할 수 있다.
확인 및 테스트
다시 만든 파드에는 새로운 볼륨이 추가된 것을 확인할 수 있다.
또한 환경변수로 몇 가지 값이 들어가는데, 가장 아래 환경변수가 있을 때 aws sdk와 cli는 알아서 웹 기반 자격 증명을 시도하게 된다.
이제는 명령이 정상적으로 수행된다는 것을 확인할 수 있다.
기다리다보면 해당 명령이 수행된 것이 클라우드트레일에도 남는다.
파드 아이덴티티
기본 확인
먼저 워커 노드에 적용되는 기본 정책에는 파드 아이덴티티 기능을 활용하기 위한 기본 권한이 있다.
k -n kube-system get daemonsets.apps eks-pod-identity-agent -oyaml
2703, 80 포트를 사용하는 데몬셋이 확인된다.
ss -ntlp
호스트 네트워크를 사용하고 있기 때문에, 직접 인스턴스에 접속해서 포트 상태를 보더라도 관련한 정보가 보인다.
특정 ip를 사용하도록 아예 세팅이 돼있는데, 가상 인터페이스를 통해 아예 고정돼있는 것도 확인할 수 있다.
테라폼설정
테라폼에서는 아래와 같이 모듈을 사용할 수 있다.[3]
module "aws_vpc_cni_ipv4_pod_identity" {
source = "../../"
name = "aws-vpc-cni-ipv4"
attach_aws_vpc_cni_policy = true
aws_vpc_cni_enable_ipv4 = true
# Pod Identity Associations
association_defaults = {
namespace = "kube-system"
service_account = "aws-node"
}
associations = {
ex-one = {
cluster_name = module.eks_one.cluster_name
}
ex-two = {
cluster_name = module.eks_two.cluster_name
}
}
tags = local.tags
}
굳이 이걸 사용해야만 하는 건 아니다.
다만 각종 애드온에 대해서 aws에서 미리 관리형으로 정책을 제공해주지 않다보니.. 귀찮은 정책을 일일히 작성하거나 받아오는 것보다는, 아무래도 간단하게 모듈로 관리하는 게 편하다는 정도의 생각이다.
(애초에 정책이랑 롤 미리 만들어주기만 했으면 굳이 모듈로 안 썼다.)
직접 설정
하지만 이에 앞서서 직접 만들어 세팅하는 것을 먼저 해보자.
eksctl create podidentityassociation \
--cluster $CLUSTER_NAME \
--namespace default \
--service-account-name my-sa \
--role-name s3-eks-pod-identity-role \
--permission-policy-arns arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--region ap-northeast-2
eksctl을 사용할 경우 매우 간단하게 어소시에이션을 만들 수 있다.
콘솔을 쓰면 원하는 네임스페이스와 서비스 어카운트를 지정하는 것이 훨씬 간단하다.
만들고 나면 콘솔에서 쉽게 해당 정보를 확인할 수 있다.
k create sa my-sa
다만 파드 아이덴티티 설정은 알아서 서비스어카운트를 만들어주지는 않으므로 직접 만들어준다.
이제 다시 파드를 생성해보면..
일단 환경변수로 또 비슷하게 값이 주입된 것을 확인할 수 있다.
크게 상관은 없겠다만 왜 굳이 저 ip를 항상 고정하도록 한 것인지 조금 궁금하다.
아울러 이번에도 서비스 어카운트 토큰이 추가적으로 들어가게 된다.
다시금 aws에 요청을 날려보면 이번에도 성공적으로 리소스 접근이 성공한 것을 확인할 수 있다!
클라우드트레일에는 podidentity 롤 설정 이벤트가 남는데, 노드의 인스턴스 프로필이 사용돼 유저 이름에 인스턴스 이름이 남는 것을 확인할 수 있다.
토큰 값 확인
keti eks-iam-test1 -- cat /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
마지막으로 실제 토큰이 어떻게 생겼는지는 jwt.io에 토큰을 넣어서 확인해볼 수 있다.
이건 기본으로 주입되는 서비스어카운트 토큰인데, 사실 페이로드에서 다른 값은 청중을 뜻하는 aud값 뿐이다.
keti eks-iam-test1 -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | openssl x509 -noout -text -pubkey
기본 서비스어카운트 토큰에 대해서는 인증서 파일이 같이 딸려오므로 공개키를 꺼낼 수 있는데, 파드 아이덴티티의 경우 마운팅이 되지 않기 때문에 비교를 할 수 없었다.
다만 어차피 api 서버에서 모든 작업이 이뤄지고, 내가 알고 있는 한 개별 서비스 어카운트에 대해 개별 인증서를 적용하는 설정이 없기 때문에 아마 같은 키로 서명되지 않았을까 생각해본다.
결론
설정하는 방식에서 IRSA, 파드 아이덴티티가 크게 차이가 없게 느껴질 수도 있다.
그러나 IRSA는 어노테이션에만 의존하기도 하고, 실제 어떤 파드가 어떤 권한을 가지게 되는지에 대해 엄격한 설정을 하려고 하면 매우 귀찮아지기도 한다.
거기에 aws 롤에서 이 롤을 취득하는 주체를 검증할 때는 그저 클러스터의 공개키로 서명됐는가만 따질 뿐이라 그다지 보안성이 높다고도 볼 수 없다.
여기에 각종 애드온 버전을 업그레이드하려고 들면 온갖 애로사항이 생기는 광경을 목격하게 될 수도 있다..
그런 면에서 확실하게 aws 측에서 신원을 따져 권한을 관리하는 파드 아이덴티티 방식은 조금 더 안전하다고 할 수 있다.
다만 Fargate에 올린 파드의 경우엔 데몬셋이 곁에 구동되지 않다보니 해당 방식을 사용할 수 없기에, 결국 IRSA 방식도 익혀둘 필요가 있다.
이전 글, 다음 글
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - EKS 설치 및 액세스 엔드포인트 변경 실습 | 1 | published | 2025-02-03 |
2W - 테라폼으로 환경 구성 및 VPC 연결 | 2 | published | 2025-02-11 |
2W - EKS VPC CNI 분석 | 3 | published | 2025-02-11 |
2W - ALB Controller, External DNS | 4 | published | 2025-02-15 |
3W - kubestr과 EBS CSI 드라이버 | 5 | published | 2025-02-21 |
3W - EFS 드라이버, 인스턴스 스토어 활용 | 6 | published | 2025-02-22 |
4W - 번외 AL2023 노드 초기화 커스텀 | 7 | published | 2025-02-25 |
4W - EKS 모니터링과 관측 가능성 | 8 | published | 2025-02-28 |
4W - 프로메테우스 스택을 통한 EKS 모니터링 | 9 | published | 2025-02-28 |
5W - HPA, KEDA를 활용한 파드 오토스케일링 | 10 | published | 2025-03-07 |
5W - Karpenter를 활용한 클러스터 오토스케일링 | 11 | published | 2025-03-07 |
6W - PKI 구조, CSR 리소스를 통한 api 서버 조회 | 12 | published | 2025-03-15 |
6W - api 구조와 보안 1 - 인증 | 13 | published | 2025-03-15 |
6W - api 보안 2 - 인가, 어드미션 제어 | 14 | published | 2025-03-16 |
6W - EKS 파드에서 AWS 리소스 접근 제어 | 15 | published | 2025-03-16 |
6W - EKS api 서버 접근 보안 | 16 | published | 2025-03-16 |
7W - 쿠버네티스의 스케줄링, 커스텀 스케줄러 설정 | 17 | published | 2025-03-22 |
7W - EKS Fargate | 18 | published | 2025-03-22 |
7W - EKS Automode | 19 | published | 2025-03-22 |
8W - 아르고 워크플로우 | 20 | published | 2025-03-30 |
8W - 아르고 롤아웃 | 21 | published | 2025-03-30 |
8W - 아르고 CD | 22 | published | 2025-03-30 |
8W - CICD | 23 | published | 2025-03-30 |
9W - EKS 업그레이드 | 24 | published | 2025-04-02 |
10W - Vault를 활용한 CICD 보안 | 25 | published | 2025-04-16 |
11W - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-04-18 |
11주차 - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-05-11 |
12W - VPC Lattice 기반 gateway api | 27 | published | 2025-04-27 |
관련 문서
이름 | noteType | created |
---|---|---|
쿠버 RBAC | knowledge | 2024-09-02 |
시큐리티 | knowledge | 2025-01-12 |
Secret | knowledge | 2025-01-12 |
파드 시큐리티 스탠다드 | knowledge | 2025-01-13 |
ServiceAccount | knowledge | 2025-01-13 |
API 접근 제어 우회 | knowledge | 2025-01-13 |
멀티 테넌시 | knowledge | 2025-01-13 |
Authentication | knowledge | 2025-01-13 |
리눅스 커널 보안 제약 | knowledge | 2025-01-14 |
보안 체크리스트 | knowledge | 2025-01-14 |
Authorization | knowledge | 2025-01-19 |
Admission Control | knowledge | 2025-01-20 |
Admission Webhook | knowledge | 2025-01-20 |
Audit | knowledge | 2025-03-12 |
Validation Admission Policy | knowledge | 2025-03-17 |
Kyverno | knowledge | 2025-03-17 |
E-쿠버네티스 인증 실습 | topic/explain | 2025-01-21 |
E-api 서버 감사 | topic/explain | 2025-01-21 |
E-쿠버 RBAC 권한 상승 방지 실습 | topic/explain | 2025-03-16 |
E-Kyverno 기본 실습 | topic/explain | 2025-03-17 |